﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace SpectrEngine.Framework
{
    public sealed class ScreenManager
        : DrawableGameComponent
    {
        #region Fields
        #endregion

        #region Local Variables

        private List<GameScreen> screens;

        private Queue<GameScreen> screensToUpdate;

        private List<GameScreen> screensToDraw;

        private HashSet<GameScreen> screensToRemove;

        #endregion

        #region Constructors

        public ScreenManager(Game game)
            : base(game)
        {
            screens = new List<GameScreen>();
            screensToDraw = new List<GameScreen>();
            screensToUpdate = new Queue<GameScreen>();
            screensToRemove = new HashSet<GameScreen>();

            game.Components.Add(this);
            game.Services.AddService(this);
        }

        #endregion

        #region Updating

        public override void Update(GameTime gameTime)
        {
            foreach (GameScreen screen in screensToRemove)
            {
                screen.Dispose();
                screens.Remove(screen);
            }

            screensToRemove.Clear();

            screensToUpdate.Clear();

            bool blocked = false;

            for (int i = screens.Count - 1; i >= 0; i--)
            {
                if (!blocked)
                {
                    screensToUpdate.Enqueue(screens[i]);
                }

                if (screens[i].BlocksUpdate)
                {
                    blocked = true;
                }
            }

            while (screensToUpdate.Count > 0)
            {
                screensToUpdate.Dequeue().Update(gameTime);
            }
        }

        #endregion

        #region Drawing

        public override void Draw(GameTime gameTime)
        {
            screensToDraw.Clear();

            bool blocked = false;

            for (int i = screens.Count - 1; i >= 0; i--)
            {
                if (!blocked)
                {
                    screensToDraw.Add(screens[i]);
                }

                if (screens[i].BlocksDraw)
                {
                    blocked = true;
                }
            }

            if (screensToDraw.Count == 0)
            {
                return;
            }

            if (screensToDraw.First().State == ScreenState.TransitionOn)
            {
                screensToDraw.First().TransitionStore(gameTime);
            }
            while (screensToDraw.Count > 1)
            {
                screensToDraw.Last().Begin();
                screensToDraw.Last().Draw(gameTime);
                screensToDraw.Last().End();
                screensToDraw.RemoveAt(screensToDraw.Count - 1);
            }
            if (screensToDraw[0].State == ScreenState.TransitionOn)
            {
                screensToDraw[0].TransitionDraw(gameTime);
            }
            else
            {
                screensToDraw[0].Begin();
                screensToDraw[0].Draw(gameTime);
                screensToDraw[0].End();
            }
        }

        #endregion

        #region Registering Nodes

        public void Register(GameNode node, int screenIndex)
        {
            if (screenIndex < screens.Count)
            {
                screens[screenIndex].Register(node);
            }
        }

        public void Register(DrawableGameNode node, int screenIndex)
        {
            if (screenIndex < screens.Count)
            {
                screens[screenIndex].Register(node);
            }
        }

        public void Register(CollidableGameNode node, int screenIndex)
        {
            if (screenIndex < screens.Count)
            {
                screens[screenIndex].Register(node);
            }
        }

        #endregion

        #region Adding/Removing Screens

        public void AddScreen(GameScreen screen)
        {
            screens.Add(screen);
        }

        public void AddScreen(GameScreen screen, int screenIndex)
        {
            screens.Insert(Math.Min(screenIndex, screens.Count), screen);
        }

        public void RemoveScreen(GameScreen screen)
        {
            screensToRemove.Add(screen);
        }

        public void RemoveScreen(int screenIndex)
        {
            if (screenIndex < screens.Count)
            {
                screensToRemove.Add(screens[screenIndex]);
            }
        }

        #endregion
    }
}
